home *** CD-ROM | disk | FTP | other *** search
/ SGI Freeware 1999 August / SGI Freeware 1999 August.iso / dist / fw_xemacs.idb / usr / freeware / lib / xemacs-20.4 / lisp / packages / gnuserv.el.z / gnuserv.el
Encoding:
Text File  |  1998-05-21  |  28.2 KB  |  780 lines

  1. ;;; gnuserv.el --- Lisp interface code between Emacs and gnuserv
  2. ;; Copyright (C) 1989-1997 Free Software Foundation, Inc.
  3.  
  4. ;; Version: 3.10
  5. ;; Author: Andy Norman (ange@hplb.hpl.hp.com), originally based on server.el
  6. ;;         Hrvoje Niksic <hniksic@srce.hr>
  7. ;; Maintainer: Jan Vroonhof <vroonhof@math.ethz.ch>,
  8. ;;             Hrvoje Niksic <hniksic@srce.hr>
  9. ;; Keywords: environment, processes, terminals
  10.  
  11. ;; This file is part of XEmacs.
  12.  
  13. ;; XEmacs is free software; you can redistribute it and/or modify it
  14. ;; under the terms of the GNU General Public License as published by
  15. ;; the Free Software Foundation; either version 2, or (at your option)
  16. ;; any later version.
  17.  
  18. ;; XEmacs is distributed in the hope that it will be useful, but
  19. ;; WITHOUT ANY WARRANTY; without even the implied warranty of
  20. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  21. ;; General Public License for more details.
  22.  
  23. ;; You should have received a copy of the GNU General Public License
  24. ;; along with XEmacs; see the file COPYING.  If not, write to the 
  25. ;; Free Software Foundation, 59 Temple Place - Suite 330,
  26. ;; Boston, MA 02111-1307, USA.
  27.  
  28. ;;; Synched up with: Not in FSF.
  29.  
  30. ;;; Commentary:
  31.  
  32. ;; Gnuserv is run when Emacs needs to operate as a server for other
  33. ;; processes.  Specifically, any number of files can be attached for
  34. ;; editing to a running XEmacs process using the `gnuclient' program.
  35.  
  36. ;; Use `M-x gnuserv-start' to start the server and `gnuclient files'
  37. ;; to load them to XEmacs.  When you are done with a buffer, press
  38. ;; `C-x #' (`M-x gnuserv-edit').  You can put (gnuserv-start) to your
  39. ;; .emacs, and enable `gnuclient' as your Unix "editor".  When all the
  40. ;; buffers for a client have been edited and exited with
  41. ;; `gnuserv-edit', the client "editor" will return to the program that
  42. ;; invoked it.
  43.  
  44. ;; Your editing commands and Emacs' display output go to and from the
  45. ;; terminal or X display in the usual way.  If you are running under
  46. ;; X, a new X frame will be open for each gnuclient.  If you are on a
  47. ;; TTY, this TTY will be attached as a new device to the running
  48. ;; XEmacs, and will be removed once you are done with the buffer.
  49.  
  50. ;; To evaluate a Lisp form in a running Emacs, use the `-eval'
  51. ;; argument of gnuclient.  To simplify this, we provide the `gnudoit'
  52. ;; shell script.  For example `gnudoit "(+ 2 3)"' will print `5',
  53. ;; whereas `gnudoit "(gnus)"' will fire up your favorite newsreader.
  54. ;; Like gnuclient, `gnudoit' requires the server to be started prior
  55. ;; to using it.
  56.  
  57. ;; For more information you can refer to man pages of gnuclient,
  58. ;; gnudoit and gnuserv, distributed with XEmacs.
  59.  
  60. ;; gnuserv.el was originally written by Andy Norman as an improvement
  61. ;; over William Sommerfeld's server.el.  Since then, a number of
  62. ;; people have worked on it, including Bob Weiner, Darell Kindred,
  63. ;; Arup Mukherjee, Ben Wing and Jan Vroonhof.  It was completely
  64. ;; rewritten (labeled as version 3) by Hrvoje Niksic in May 1997.  The
  65. ;; new code will not run on GNU Emacs.
  66.  
  67. ;; Jan Vroonhof <vroonhof@math.ethz.ch> July/1996
  68. ;; ported the server-temp-file-regexp feature from server.el
  69. ;; ported server hooks from server.el
  70. ;; ported kill-*-query functions from server.el (and made it optional)
  71. ;; synced other behaviour with server.el
  72. ;;
  73. ;; Jan Vroonhof
  74. ;;     Customized.
  75. ;;
  76. ;; Hrvoje Niksic <hniksic@srce.hr> May/1997
  77. ;;     Completely rewritten.  Now uses `defstruct' and other CL stuff
  78. ;;     to define clients cleanly.  Many thanks to Dave Gillespie!
  79. ;;
  80. ;; Mike Scheidler <c23mts@eng.delcoelect.com> July, 1997
  81. ;;     Added 'Done' button to the menubar.
  82.  
  83.  
  84. ;;; Code:
  85.  
  86. (defgroup gnuserv nil
  87.   "The gnuserv suite of programs to talk to Emacs from outside."
  88.   :group 'environment
  89.   :group 'processes
  90.   :group 'terminals)
  91.  
  92.  
  93. ;; Provide the old variables as aliases, to avoid breaking .emacs
  94. ;; files.  However, they are obsolete and should be converted to the
  95. ;; new forms.  This ugly crock must be before the variable
  96. ;; declaration, or the scheme fails.
  97.  
  98. (define-obsolete-variable-alias 'server-frame 'gnuserv-frame)
  99. (define-obsolete-variable-alias 'server-done-function
  100.   'gnuserv-done-function)
  101. (define-obsolete-variable-alias 'server-done-temp-file-function
  102.   'gnuserv-done-temp-file-function)
  103. (define-obsolete-variable-alias 'server-find-file-function
  104.   'gnuserv-find-file-function)
  105. (define-obsolete-variable-alias 'server-program
  106.   'gnuserv-program)
  107. (define-obsolete-variable-alias 'server-visit-hook
  108.   'gnuserv-visit-hook)
  109. (define-obsolete-variable-alias 'server-done-hook
  110.   'gnuserv-done-hook)
  111. (define-obsolete-variable-alias 'server-kill-quietly
  112.   'gnuserv-kill-quietly)
  113. (define-obsolete-variable-alias 'server-temp-file-regexp
  114.   'gnuserv-temp-file-regexp)
  115. (define-obsolete-variable-alias 'server-make-temp-file-backup
  116.   'gnuserv-make-temp-file-backup)
  117.  
  118. ;;;###autoload
  119. (defcustom gnuserv-frame nil
  120.   "*The frame to be used to display all edited files.
  121. If nil, then a new frame is created for each file edited.
  122. If t, then the currently selected frame will be used.
  123. If a function, then this will be called with a symbol `x' or `tty' as the
  124. only argument, and its return value will be interpreted as above."
  125.   :tag "Gnuserv Frame"
  126.   :type '(radio (const :tag "Create new frame each time" nil)
  127.         (const :tag "Use selected frame" t)
  128.             (function-item :tag "Use main Emacs frame"
  129.                    gnuserv-main-frame-function)
  130.         (function-item :tag "Use visible frame, otherwise create new"
  131.                    gnuserv-visible-frame-function)
  132.         (function-item :tag "Create special Gnuserv frame and use it"
  133.                    gnuserv-special-frame-function)
  134.         (function :tag "Other"))
  135.   :group 'gnuserv
  136.   :group 'frames)
  137.  
  138. (defcustom gnuserv-frame-plist nil
  139.   "*Plist of frame properties for creating a gnuserv frame."
  140.   :type '(repeat (group :inline t
  141.             (symbol :tag "Property")
  142.             (sexp :tag "Value")))
  143.   :group 'gnuserv
  144.   :group 'frames)
  145.  
  146. (defcustom gnuserv-done-function 'kill-buffer 
  147.   "*Function used to remove a buffer after editing.
  148. It is called with one BUFFER argument.  Functions such as `kill-buffer' and
  149. `bury-buffer' are good values. See also `gnuserv-done-temp-file-function'."
  150.   :type '(radio (function-item kill-buffer)
  151.         (function-item bury-buffer)
  152.         (function :tag "Other"))
  153.   :group 'gnuserv)
  154.  
  155. (defcustom gnuserv-done-temp-file-function 'kill-buffer
  156.   "*Function used to remove a temporary buffer after editing.
  157. It is called with one BUFFER argument.  Functions such as `kill-buffer' and
  158. `bury-buffer' are good values. See also `gnuserv-done-temp-file-function'."
  159.   :type '(radio (function-item kill-buffer)
  160.         (function-item bury-buffer)
  161.         (function :tag "Other"))
  162.   :group 'gnuserv)
  163.  
  164. (defcustom gnuserv-find-file-function 'find-file
  165.   "*Function to visit a file with.
  166. It takes one argument, a file name to visit."
  167.   :type 'function
  168.   :group 'gnuserv)
  169.  
  170. (defcustom gnuserv-view-file-function 'view-file
  171.   "*Function to view a file with.
  172. It takes one argument, a file name to view."
  173.   :type '(radio (function-item view-file)
  174.         (function-item find-file-read-only)
  175.         (function :tag "Other"))
  176.   :group 'gnuserv)
  177.  
  178. (defcustom gnuserv-program "gnuserv"
  179.   "*Program to use as the editing server."
  180.   :type 'string
  181.   :group 'gnuserv)
  182.  
  183. (defcustom gnuserv-visit-hook nil
  184.   "*Hook run after visiting a file."
  185.   :type 'hook
  186.   :group 'gnuserv)
  187.  
  188. (defcustom gnuserv-done-hook nil
  189.   "*Hook run when done editing a buffer for the Emacs server.
  190. The hook functions are called after the file has been visited, with the
  191. current buffer set to the visiting buffer."
  192.   :type 'hook
  193.   :group 'gnuserv)
  194.  
  195. (defcustom gnuserv-init-hook nil
  196.   "*Hook run after the server is started."
  197.   :type 'hook
  198.   :group 'gnuserv)
  199.  
  200. (defcustom gnuserv-shutdown-hook nil
  201.   "*Hook run before the server exits."
  202.   :type 'hook
  203.   :group 'gnuserv)
  204.  
  205. (defcustom gnuserv-kill-quietly nil
  206.   "*Non-nil means to kill buffers with clients attached without requiring confirmation."
  207.   :type 'boolean
  208.   :group 'gnuserv)
  209.  
  210. (defcustom gnuserv-temp-file-regexp "^/tmp/Re\\|/draft$"
  211.   "*Regexp which should match filenames of temporary files deleted
  212. and reused by the programs that invoke the Emacs server."
  213.   :type 'regexp
  214.   :group 'gnuserv)
  215.  
  216. (defcustom gnuserv-make-temp-file-backup nil
  217.   "*Non-nil makes the server backup temporary files also."
  218.   :type 'boolean
  219.   :group 'gnuserv)
  220.  
  221.  
  222. ;;; Internal variables:
  223.  
  224. (defstruct gnuclient
  225.   "An object that encompasses several buffers in one.
  226. Normally, a client connecting to Emacs will be assigned an id, and
  227. will request editing of several files.
  228.  
  229. ID       - Client id (integer).
  230. BUFFERS  - List of buffers that \"belong\" to the client.
  231.            NOTE: one buffer can belong to several clients.
  232. DEVICE   - The device this client is on.  If the device was also created.
  233.            by a client, it will be placed to `gnuserv-devices' list.
  234. FRAME    - Frame created by the client, or nil if the client didn't
  235.            create a frame.
  236.  
  237. All the slots default to nil."
  238.   (id nil)
  239.   (buffers nil)
  240.   (device nil)
  241.   (frame nil))
  242.  
  243. (defvar gnuserv-process nil 
  244.   "The current gnuserv process.")
  245.  
  246. (defvar gnuserv-string ""
  247.   "The last input string from the server.")
  248.  
  249. (defvar gnuserv-current-client nil
  250.   "The client we are currently talking to.")
  251.  
  252. (defvar gnuserv-clients nil
  253.   "List of current gnuserv clients.
  254. Each element is a gnuclient structure that identifies a client.")
  255.  
  256. (defvar gnuserv-devices nil
  257.   "List of devices created by clients.")
  258.  
  259. (defvar gnuserv-special-frame nil
  260.   "Frame created specially for Server.")
  261.  
  262. ;; We want the client-infested buffers to have some modeline
  263. ;; identification, so we'll make a "minor mode".
  264. (defvar gnuserv-minor-mode nil)
  265. (make-variable-buffer-local 'gnuserv-mode)
  266. (pushnew '(gnuserv-minor-mode " Server") minor-mode-alist
  267.       :test 'equal)
  268.  
  269.  
  270. ;; Sample gnuserv-frame functions
  271.  
  272. (defun gnuserv-main-frame-function (type)
  273.   "Returns a sensible value for the main Emacs frame."
  274.   (if (eq type 'x)
  275.       (car (frame-list))
  276.     nil))
  277.  
  278. (defun gnuserv-visible-frame-function (type)
  279.   "Returns a frame if there is a frame that is truly visible, nil otherwise.
  280. This is meant in the X sense, so it will not return frames that are on another
  281. visual screen.  Totally visible frames are preferred.  If none found, return nil."
  282.   (if (eq type 'x)
  283.       (cond ((car (filtered-frame-list 'frame-totally-visible-p
  284.                        (selected-device))))
  285.         ((car (filtered-frame-list (lambda (frame)
  286.                      ;; eq t as in not 'hidden
  287.                      (eq t (frame-visible-p frame)))
  288.                        (selected-device)))))
  289.     nil))
  290.  
  291. (defun gnuserv-special-frame-function (type)
  292.   "Creates a special frame for Gnuserv and returns it on later invocations."
  293.   (unless (frame-live-p gnuserv-special-frame)
  294.     (setq gnuserv-special-frame (make-frame gnuserv-frame-plist)))
  295.   gnuserv-special-frame)
  296.  
  297.  
  298. ;;; Communication functions
  299.  
  300. ;; We used to restart the server here, but it's too risky -- if
  301. ;; something goes awry, it's too easy to wind up in a loop.
  302. (defun gnuserv-sentinel (proc msg)
  303.   (let ((msgstring (concat "Gnuserv process %s; restart with `%s'"))
  304.     (keystring (substitute-command-keys "\\[gnuserv-start]")))
  305.   (case (process-status proc)
  306.     (exit
  307.      (message msgstring "exited" keystring)
  308.      (gnuserv-prepare-shutdown))
  309.     (signal
  310.      (message msgstring "killed" keystring)
  311.      (gnuserv-prepare-shutdown))
  312.     (closed
  313.      (message msgstring "closed" keystring))
  314.      (gnuserv-prepare-shutdown))))
  315.  
  316. ;; This function reads client requests from our current server.  Every
  317. ;; client is identified by a unique ID within the server
  318. ;; (incidentally, the same ID is the file descriptor the server uses
  319. ;; to communicate to client).
  320. ;;
  321. ;; The request string can arrive in several chunks.  As the request
  322. ;; ends with \C-d, we check for that character at the end of string.
  323. ;; If not found, keep reading, and concatenating to former strings.
  324. ;; So, if at first read we receive "5 (gn", that text will be stored
  325. ;; to gnuserv-string.  If we then receive "us)\C-d", the two will be
  326. ;; concatenated, `current-client' will be set to 5, and `(gnus)' form
  327. ;; will be evaluated.
  328. ;;
  329. ;; Server will send the following:
  330. ;;
  331. ;; "ID <text>\C-d"  (no quotes)
  332. ;;
  333. ;;  ID    - file descriptor of the given client;
  334. ;; <text> - the actual contents of the request.
  335. (defun gnuserv-process-filter (proc string)
  336.   "Process gnuserv client requests to execute Emacs commands."
  337.   (setq gnuserv-string (concat gnuserv-string string))
  338.   ;; C-d means end of request.
  339.   (when (string-match "\C-d\\'" gnuserv-string)
  340.     (cond ((string-match "^[0-9]+" gnuserv-string) ; client request id
  341.        (let ((header (read-from-string gnuserv-string)))
  342.          ;; Set the client we are talking to.
  343.          (setq gnuserv-current-client (car header))
  344.          ;; Evaluate the expression
  345.          (condition-case oops
  346.          (eval (car (read-from-string gnuserv-string (cdr header))))
  347.            ;; In case of an error, write the description to the
  348.            ;; client, and then signal it.
  349.            (error (setq gnuserv-string "")
  350.               (gnuserv-write-to-client gnuserv-current-client oops)
  351.               (setq gnuserv-current-client nil)
  352.               (signal (car oops) (cdr oops)))
  353.            (quit (setq gnuserv-string "")
  354.              (gnuserv-write-to-client gnuserv-current-client oops)
  355.              (setq gnuserv-current-client nil)
  356.              (signal 'quit nil)))
  357.          (setq gnuserv-string "")))
  358.       (t
  359.        (error "%s: invalid response from gnuserv" gnuserv-string)
  360.        (setq gnuserv-string "")))))
  361.  
  362. ;; This function is somewhat of a misnomer.  Actually, we write to the
  363. ;; server (using `process-send-string' to gnuserv-process), which
  364. ;; interprets what we say and forwards it to the client.  The
  365. ;; incantation server understands is (from gnuserv.c):
  366. ;;
  367. ;; "FD/LEN:<text>\n"  (no quotes)
  368. ;;    FD     - file descriptor of the given client (which we obtained from
  369. ;;             the server earlier);
  370. ;;    LEN    - length of the stuff we are about to send;
  371. ;;    <text> - the actual contents of the request.
  372. (defun gnuserv-write-to-client (client-id form)
  373.   "Write the given form to the given client via the gnuserv process."
  374.   (when (eq (process-status gnuserv-process) 'run)
  375.     (let* ((result (format "%s" form))
  376.        (s      (format "%s/%d:%s\n" client-id
  377.                (length result) result)))
  378.       (process-send-string gnuserv-process s))))
  379.  
  380. ;; The following two functions are helper functions, used by
  381. ;; gnuclient.
  382.  
  383. (defun gnuserv-eval (form)
  384.   "Evaluate form and return result to client."
  385.   (gnuserv-write-to-client gnuserv-current-client (eval form))
  386.   (setq gnuserv-current-client nil))
  387.  
  388. (defun gnuserv-eval-quickly (form)
  389.   "Let client know that we've received the request, and then eval the form.
  390. This order is important as not to keep the client waiting."
  391.   (gnuserv-write-to-client gnuserv-current-client nil)
  392.   (setq gnuserv-current-client nil)
  393.   (eval form))
  394.  
  395.  
  396. ;; "Execute" a client connection, called by gnuclient.  This is the
  397. ;; backbone of gnuserv.el.
  398. (defun gnuserv-edit-files (type list &rest flags)
  399.   "For each (line-number . file) pair in LIST, edit the file at line-number.
  400. The visited buffers are memorized, so that when \\[gnuserv-edit] is invoked
  401. in such a buffer, or when it is killed, or the client's device deleted, the
  402. client will be invoked that the edit is finished.
  403.  
  404. TYPE should either be a (tty TTY TERM PID) list, or (x DISPLAY) list.
  405. If a flag is `quick', just edit the files in Emacs.
  406. If a flag is `view', view the files read-only."
  407.   (let (quick view)
  408.     (mapc (lambda (flag)
  409.         (case flag
  410.           (quick (setq quick t))
  411.           (view  (setq view t))
  412.           (t     (error "Invalid flag %s" flag))))
  413.       flags)
  414.     (let* ((old-device-num (length (device-list)))
  415.        (new-frame nil)
  416.        (dest-frame (if (functionp gnuserv-frame)
  417.                (funcall gnuserv-frame (car type))
  418.              gnuserv-frame))
  419.        ;; The gnuserv-frame dependencies are ugly, but it's
  420.        ;; extremely hard to make that stuff cleaner without
  421.        ;; breaking everything in sight.
  422.        (device (cond ((frame-live-p dest-frame)
  423.               (frame-device dest-frame))
  424.              ((null dest-frame)
  425.               (case (car type)
  426.                 (tty (apply 'make-tty-device (cdr type)))
  427.                 (x   (make-x-device (cadr type)))
  428.                 (t   (error "Invalid device type"))))
  429.              (t
  430.               (selected-device))))
  431.        (frame (cond ((frame-live-p dest-frame)
  432.              dest-frame)
  433.             ((null dest-frame)
  434.              (setq new-frame (make-frame gnuserv-frame-plist
  435.                              device))
  436.              new-frame)
  437.             (t (selected-frame))))
  438.        (client (make-gnuclient :id gnuserv-current-client
  439.                    :device device
  440.                    :frame new-frame)))
  441.       (setq gnuserv-current-client nil)
  442.       ;; If the device was created by this client, push it to the list.
  443.       (and (/= old-device-num (length (device-list)))
  444.        (push device gnuserv-devices))
  445.       (and (frame-iconified-p frame)
  446.        (deiconify-frame frame))
  447.       ;; Visit all the listed files.
  448.       (while list
  449.     (let ((line (caar list)) (path (cdar list)))
  450.       (select-frame frame)
  451.       ;; Visit the file.
  452.       (funcall (if view
  453.                gnuserv-view-file-function
  454.              gnuserv-find-file-function)
  455.            path)
  456.       (goto-line line)
  457.       ;; Don't memorize the quick and view buffers.
  458.       (unless (or quick view)
  459.         (pushnew (current-buffer) (gnuclient-buffers client))
  460.         (setq gnuserv-minor-mode t)
  461.         ;; Add the "Done" button to the menubar, only in this buffer.
  462.         (if (and (featurep 'menubar) current-menubar)
  463.           (progn (set-buffer-menubar current-menubar)
  464.           (add-menu-button nil ["Done" gnuserv-edit t]))
  465.           ))
  466.       (run-hooks 'gnuserv-visit-hook)
  467.       (pop list)))
  468.       (cond
  469.        ((and (or quick view)
  470.          (device-on-window-system-p device))
  471.     ;; Exit if on X device, and quick or view.  NOTE: if the
  472.     ;; client is to finish now, it must absolutely /not/ be
  473.     ;; included to the list of clients.  This way the client-ids
  474.     ;; should be unique.
  475.     (gnuserv-write-to-client (gnuclient-id client) nil))
  476.        (t
  477.     ;; Else, the client gets a vote.
  478.     (push client gnuserv-clients)
  479.     ;; Explain buffer exit options.  If dest-frame is nil, the
  480.     ;; user can exit via `delete-frame'.  OTOH, if FLAGS are nil
  481.     ;; and there are some buffers, the user can exit via
  482.     ;; `gnuserv-edit'.
  483.     (if (and (not (or quick view))
  484.          (gnuclient-buffers client))
  485.         (message "%s"
  486.              (substitute-command-keys
  487.               "Type `\\[gnuserv-edit]' to finish editing"))
  488.       (or dest-frame
  489.           (message "%s"
  490.                (substitute-command-keys
  491.             "Type `\\[delete-frame]' to finish editing")))))))))
  492.  
  493.  
  494. ;;; Functions that hook into Emacs in various way to enable operation
  495.  
  496. ;; Defined later.
  497. (add-hook 'kill-emacs-hook 'gnuserv-kill-all-clients t)
  498.  
  499. ;; A helper function; used by others.  Try avoiding it whenever
  500. ;; possible, because it is slow, and conses a list.  Use
  501. ;; `gnuserv-buffer-p' when appropriate, for instance.
  502. (defun gnuserv-buffer-clients (buffer)
  503.   "Returns a list of clients to which BUFFER belongs."
  504.   (let (res)
  505.     (dolist (client gnuserv-clients)
  506.       (when (memq buffer (gnuclient-buffers client))
  507.     (push client res)))
  508.     res))
  509.  
  510. ;; Like `gnuserv-buffer-clients', but returns a boolean; doesn't
  511. ;; collect a list.
  512. (defun gnuserv-buffer-p (buffer)
  513.   (member* buffer gnuserv-clients
  514.        :test 'memq
  515.        :key 'gnuclient-buffers))
  516.  
  517. ;; This function makes sure that a killed buffer is deleted off the
  518. ;; list for the particular client.
  519. ;;
  520. ;; This hooks into `kill-buffer-hook'.  It is *not* a replacement for
  521. ;; `kill-buffer' (thanks God).
  522. (defun gnuserv-kill-buffer-function ()
  523.   "Remove the buffer from the buffer lists of all the clients it belongs to.
  524. Any client that remains \"empty\" after the removal is informed that the
  525. editing has ended."
  526.   (let* ((buf (current-buffer)))
  527.     (dolist (client (gnuserv-buffer-clients buf))
  528.       (callf2 delq buf (gnuclient-buffers client))
  529.       ;; If no more buffers, kill the client.
  530.       (when (null (gnuclient-buffers client))
  531.     (gnuserv-kill-client client)))))
  532.  
  533. (add-hook 'kill-buffer-hook 'gnuserv-kill-buffer-function)
  534.  
  535. ;; Ask for confirmation before killing a buffer that belongs to a
  536. ;; living client.
  537. (defun gnuserv-kill-buffer-query-function ()
  538.   (or gnuserv-kill-quietly
  539.       (not (gnuserv-buffer-p (current-buffer)))
  540.       (yes-or-no-p
  541.        (format "Buffer %s belongs to gnuserv client(s); kill anyway? "
  542.            (current-buffer)))))
  543.  
  544. (add-hook 'kill-buffer-query-functions
  545.       'gnuserv-kill-buffer-query-function)
  546.  
  547. (defun gnuserv-kill-emacs-query-function ()
  548.   (or gnuserv-kill-quietly
  549.       (not (some 'gnuclient-buffers gnuserv-clients))
  550.       (yes-or-no-p "Gnuserv buffers still have clients; exit anyway? ")))
  551.  
  552. (add-hook 'kill-emacs-query-functions
  553.       'gnuserv-kill-emacs-query-function)
  554.  
  555. ;; If the device of a client is to be deleted, the client should die
  556. ;; as well.  This is why we hook into `delete-device-hook'.
  557. (defun gnuserv-check-device (device)
  558.   (when (memq device gnuserv-devices)
  559.     (dolist (client gnuserv-clients)
  560.       (when (eq device (gnuclient-device client))
  561.     ;; we must make sure that the server kill doesn't result in
  562.     ;; killing the device, because it would cause a device-dead
  563.     ;; error when `delete-device' tries to do the job later.
  564.     (gnuserv-kill-client client t))))
  565.   (callf2 delq device gnuserv-devices))
  566.  
  567. (add-hook 'delete-device-hook 'gnuserv-check-device)
  568.  
  569. (defun gnuserv-temp-file-p (buffer)
  570.   "Return non-nil if BUFFER contains a file considered temporary.
  571. These are files whose names suggest they are repeatedly
  572. reused to pass information to another program.
  573.  
  574. The variable `gnuserv-temp-file-regexp' controls which filenames
  575. are considered temporary."
  576.   (and (buffer-file-name buffer)
  577.        (string-match gnuserv-temp-file-regexp (buffer-file-name buffer))))
  578.  
  579. (defun gnuserv-kill-client (client &optional leave-frame)
  580.   "Kill the gnuclient CLIENT.
  581. This will do away with all the associated buffers.  If LEAVE-FRAME,
  582. the function will not remove the frames associated with the client."
  583.   ;; Order is important: first delete client from gnuserv-clients, to
  584.   ;; prevent gnuserv-buffer-done-1 calling us recursively.
  585.   (callf2 delq client gnuserv-clients)
  586.   ;; Process the buffers.
  587.   (mapc 'gnuserv-buffer-done-1 (gnuclient-buffers client))
  588.   (unless leave-frame
  589.     (let ((device (gnuclient-device client)))
  590.       ;; kill frame created by this client (if any), unless
  591.       ;; specifically requested otherwise.
  592.       ;;
  593.       ;; note: last frame on a device will not be deleted here.
  594.     (when (and (gnuclient-frame client)
  595.            (frame-live-p (gnuclient-frame client))
  596.            (second (device-frame-list device)))
  597.       (delete-frame (gnuclient-frame client)))
  598.     ;; If the device is live, created by a client, and no longer used
  599.     ;; by any client, delete it.
  600.     (when (and (device-live-p device)
  601.            (memq device gnuserv-devices)
  602.            (second (device-list))
  603.            (not (member* device gnuserv-clients
  604.                  :key 'gnuclient-device)))
  605.       ;; `gnuserv-check-device' will remove it from `gnuserv-devices'.
  606.       (delete-device device))))
  607.   ;; Notify the client.
  608.   (gnuserv-write-to-client (gnuclient-id client) nil))
  609.  
  610. ;; Do away with the buffer.
  611. (defun gnuserv-buffer-done-1 (buffer)
  612.   (dolist (client (gnuserv-buffer-clients buffer))
  613.     (callf2 delq buffer (gnuclient-buffers client))
  614.     (when (null (gnuclient-buffers client))
  615.       (gnuserv-kill-client client)))
  616.   ;; Get rid of the buffer.
  617.   (save-excursion
  618.     (set-buffer buffer)
  619.     (run-hooks 'gnuserv-done-hook)
  620.     (setq gnuserv-minor-mode nil)
  621.     ;; Delete the menu button.
  622.     (if (and (featurep 'menubar) current-menubar)
  623.       (delete-menu-item '("Done")))
  624.     (funcall (if (gnuserv-temp-file-p buffer)
  625.          gnuserv-done-temp-file-function
  626.            gnuserv-done-function)
  627.          buffer)))
  628.  
  629.  
  630. ;;; Higher-level functions
  631.  
  632. ;; Choose a `next' server buffer, according to several criteria, and
  633. ;; return it.  If none are found, return nil.
  634. (defun gnuserv-next-buffer ()
  635.   (let* ((frame (selected-frame))
  636.      (device (selected-device))
  637.      client)
  638.     (cond
  639.      ;; If we have a client belonging to this frame, return
  640.      ;; the first buffer from it.
  641.      ((setq client
  642.         (car (member* frame gnuserv-clients :key 'gnuclient-frame)))
  643.       (car (gnuclient-buffers client)))
  644.      ;; Else, look for a device.
  645.      ((and
  646.        (memq (selected-device) gnuserv-devices)
  647.        (setq client
  648.          (car (member* device gnuserv-clients :key 'gnuclient-device))))
  649.       (car (gnuclient-buffers client)))
  650.      ;; Else, try to find any client with at least one buffer, and
  651.      ;; return its first buffer.
  652.      ((setq client
  653.         (car (member-if-not #'null gnuserv-clients
  654.                 :key 'gnuclient-buffers)))
  655.       (car (gnuclient-buffers client)))
  656.      ;; Oh, give up.
  657.      (t nil))))
  658.  
  659. (defun gnuserv-buffer-done (buffer)
  660.   "Mark BUFFER as \"done\" for its client(s).
  661. Does the save/backup queries first, and calls `gnuserv-done-function'."
  662.   ;; Check whether this is the real thing.
  663.   (unless (gnuserv-buffer-p buffer)
  664.     (error "%s does not belong to a gnuserv client" buffer))
  665.   ;; Backup/ask query.
  666.   (if (gnuserv-temp-file-p buffer)
  667.       ;; For a temp file, save, and do NOT make a non-numeric backup
  668.       ;; Why does server.el explicitly back up temporary files?
  669.       (let ((version-control nil)
  670.         (buffer-backed-up (not gnuserv-make-temp-file-backup)))
  671.     (save-buffer))
  672.     (if (and (buffer-modified-p)
  673.          (y-or-n-p (concat "Save file " buffer-file-name "? ")))
  674.     (save-buffer buffer)))
  675.   (gnuserv-buffer-done-1 buffer))
  676.  
  677. ;; Called by `gnuserv-start-1' to clean everything.  Hooked into
  678. ;; `kill-emacs-hook', too.
  679. (defun gnuserv-kill-all-clients ()
  680.   "Kill all the gnuserv clients.  Ruthlessly."
  681.   (mapc 'gnuserv-kill-client gnuserv-clients))
  682.  
  683. ;; This serves to run the hook and reset
  684. ;; `allow-deletion-of-last-visible-frame'.
  685. (defun gnuserv-prepare-shutdown ()
  686.   (setq allow-deletion-of-last-visible-frame nil)
  687.   (run-hooks 'gnuserv-shutdown-hook))
  688.  
  689. ;; This is a user-callable function, too.
  690. (defun gnuserv-shutdown ()
  691.   "Shutdown the gnuserv server, if one is currently running.
  692. All the clients will be disposed of via the normal methods."
  693.   (interactive)
  694.   (gnuserv-kill-all-clients)
  695.   (when gnuserv-process
  696.     (set-process-sentinel gnuserv-process nil)
  697.     (gnuserv-prepare-shutdown)
  698.     (condition-case ()
  699.     (delete-process gnuserv-process)
  700.       (error nil))
  701.     (setq gnuserv-process nil)))
  702.  
  703. ;; Actually start the process.  Kills all the clients before-hand.
  704. (defun gnuserv-start-1 (&optional leave-dead)
  705.   ;; Shutdown the existing server, if any.
  706.   (gnuserv-shutdown)
  707.   ;; If we already had a server, clear out associated status.
  708.   (unless leave-dead
  709.     (setq gnuserv-string ""
  710.       gnuserv-current-client nil)
  711.     (let ((process-connection-type t))
  712.       (setq gnuserv-process
  713.         (start-process "gnuserv" nil gnuserv-program)))
  714.     (set-process-sentinel gnuserv-process 'gnuserv-sentinel)
  715.     (set-process-filter gnuserv-process 'gnuserv-process-filter)
  716.     (process-kill-without-query gnuserv-process)
  717.     (setq allow-deletion-of-last-visible-frame t)
  718.     (run-hooks 'gnuserv-init-hook)))
  719.  
  720.  
  721. ;;; User-callable functions:
  722.  
  723. ;;;###autoload
  724. (defun gnuserv-running-p ()
  725.   "Return non-nil if a gnuserv process is running from this XEmacs session."
  726.   (not (not gnuserv-process)))
  727.  
  728. ;;;###autoload
  729. (defun gnuserv-start (&optional leave-dead)
  730.   "Allow this Emacs process to be a server for client processes.
  731. This starts a gnuserv communications subprocess through which
  732. client \"editors\" (gnuclient and gnudoit) can send editing commands to 
  733. this Emacs job.  See the gnuserv(1) manual page for more details.
  734.  
  735. Prefix arg means just kill any existing server communications subprocess."
  736.   (interactive "P")
  737.   (and gnuserv-process
  738.        (not leave-dead)
  739.        (message "Restarting gnuserv"))
  740.   (gnuserv-start-1 leave-dead))
  741.  
  742. (defun gnuserv-edit (&optional count)
  743.   "Mark the current gnuserv editing buffer as \"done\", and switch to next one.
  744.  
  745. Run with a numeric prefix argument, repeat the operation that number
  746. of times.  If given a universal prefix argument, close all the buffers
  747. of this buffer's clients.
  748.  
  749. The `gnuserv-done-function' (bound to `kill-buffer' by default) is
  750. called to dispose of the buffer after marking it as done.
  751.  
  752. Files that match `gnuserv-temp-file-regexp' are considered temporary and
  753. are saved unconditionally and backed up if `gnuserv-make-temp-file-backup'
  754. is non-nil.  They are disposed of using `gnuserv-done-temp-file-function'
  755. (also bound to `kill-buffer' by default).
  756.  
  757. When all of a client's buffers are marked as \"done\", the client is notified."
  758.   (interactive "P")
  759.   (when (null count)
  760.     (setq count 1))
  761.   (cond ((numberp count)
  762.      (let (next)
  763.        (while (natnump (decf count))
  764.          (gnuserv-buffer-done (current-buffer))
  765.          (setq next (gnuserv-next-buffer))
  766.          (when next
  767.            (switch-to-buffer next)))))
  768.     (count
  769.        (let* ((buf (current-buffer))
  770.           (clients (gnuserv-buffer-clients buf)))
  771.          (unless clients
  772.            (error "%s does not belong to a gnuserv client" buf))
  773.          (mapc 'gnuserv-kill-client (gnuserv-buffer-clients buf))))))
  774.  
  775. (global-set-key "\C-x#" 'gnuserv-edit)
  776.  
  777. (provide 'gnuserv)
  778.  
  779. ;;; gnuserv.el ends here
  780.